﻿Imports EvoLib.Protocol
Imports MirEvoSvr.Functions
Imports System.IO
Imports System.Xml
Imports System.Net
Imports System.Windows.Forms

Namespace Socket.HTTPStats.Data

    Module ServerStats

        Public Class ServerStatistics

            Dim mStatType As ServerStat

            Public ReadOnly Property StatType() As ServerStat
                Get
                    Return mStatType
                End Get
            End Property

            Dim mValue As Object

            Public Property Value() As Object
                Get
                    Return mValue
                End Get
                Set(value As Object)
                    mValue = value
                End Set
            End Property

            Public Sub New(info As RefreshStat)

                mStatType = info.ID_STAT

                If Not (info.STR_VALUE Is Nothing) Then
                    If info.STR_VALUE.Length > 0 Then
                        mValue = info.STR_VALUE
                    Else
                        mValue = info.INT_VALUE
                    End If
                Else
                    mValue = info.INT_VALUE
                End If

            End Sub

        End Class

        Public Class ServerData

            Public Enum ServerTypes
                Cluster = 0
                Login = 1
            End Enum

            Dim mServerName As String
            Dim mHostName As String
            Dim mPort As Integer
            Dim mServerType As ServerTypes

            Public Stats As SortedDictionary(Of ServerStat, ServerStatistics)

            Dim objLock As New Object

            Public ReadOnly Property ServerName() As String
                Get
                    Return mServerName
                End Get
            End Property

            Public ReadOnly Property HostName() As String
                Get
                    Return mHostName
                End Get
            End Property

            Public ReadOnly Property Port() As Integer
                Get
                    Return mPort
                End Get
            End Property

            Public ReadOnly Property ServerType() As ServerTypes
                Get
                    Return mServerType
                End Get
            End Property

            Public Sub New(svrname As String, hostname As String, port As Integer, Optional loginsvr As Boolean = False)

                mServerName = svrname
                mHostName = hostname
                mPort = port

                If loginsvr Then
                    mServerType = ServerTypes.Login
                Else
                    mServerType = ServerTypes.Cluster
                End If

                Stats = New SortedDictionary(Of ServerStat, ServerStatistics)

            End Sub

            Public Function GetStat(stat As ServerStat) As ServerStatistics

                SyncLock objLock

                    If Stats.ContainsKey(stat) Then
                        Return Stats.Item(stat)
                    Else
                        Dim info As New RefreshStat
                        info.ID_STAT = stat
                        info.STR_HOSTNAME = mServerName
                        Refresh(info)
                        Return Stats.Item(stat)
                    End If

                End SyncLock

            End Function

            Public Sub Refresh(info As RefreshStat)

                SyncLock objLock

                    If Stats.ContainsKey(info.ID_STAT) Then
                        Dim S As ServerStatistics = Stats.Item(info.ID_STAT)
                        Stats.Remove(info.ID_STAT)
                        If Not (info.STR_VALUE Is Nothing) Then
                            If info.STR_VALUE.Length > 0 Then
                                S.Value = info.STR_VALUE
                            Else
                                S.Value = info.INT_VALUE
                            End If
                        Else
                            S.Value = info.INT_VALUE
                        End If
                        Stats.Add(info.ID_STAT, S)
                    Else

                        Stats.Add(info.ID_STAT, New ServerStatistics(info))

                    End If

                End SyncLock

            End Sub

        End Class

        Public Class ClusterStats

            Public Shared Hosts As SortedDictionary(Of String, ServerData)

            Public Shared Sub LoadHosts()

                Try

                    Hosts = New SortedDictionary(Of String, ServerData)

                    Dim C As Integer = Globals.iniServer.GetInteger("SERVER", "SERVERCOUNT", 0)

                    Dim svrname As String
                    Dim host As String
                    Dim port As Integer

                    If C > 0 Then

                        For idx As Integer = 1 To C

                            svrname = Globals.iniServer.GetString("SERVER", "SERVERNAME" & idx, "")
                            host = Globals.iniServer.GetString("SERVER", "SERVERHOST" & idx, "")
                            port = Globals.iniServer.GetString("SERVER", "SERVERPORT" & idx, "")

                            If svrname = Globals.iniServer.GetString("SERVER", "LOGINSERVER", "") And svrname <> "" Then
                                Hosts.Add(svrname, New ServerData(svrname, host, port, True))
                            Else
                                Hosts.Add(svrname, New ServerData(svrname, host, port))
                            End If

                        Next

                    End If

                    Output.AddLine("\B0\F5SV>\F0\B8 ClusterStats hosts initialized!")

                Catch ex As Exception

                    Output.AddLine("\B0\F5SV>\F0\B8 \F7ClusterStats hosts error\F0!")

                End Try

            End Sub

            Public Shared Sub LocalStatUpdate(stat As ServerStat, value As Int64)

                Dim refreshstruct As New RefreshStat

                With refreshstruct
                    .ID_STAT = stat
                    .STR_HOSTNAME = Globals.ServerName
                    .INT_VALUE = value
                End With

                If Hosts.ContainsKey(Globals.ServerName) Then
                    Hosts.Item(Globals.ServerName).Refresh(refreshstruct)
                Else
                    Dim Host As ServerData = New ServerData(Globals.ServerName, Globals.ServerName, Globals.ServerPort, Globals.isLoginServer)
                    Hosts.Add(Globals.ServerName, Host)
                End If

            End Sub

            Public Shared Sub LocalStatUpdate(stat As ServerStat, value As String)

                Dim refreshstruct As New RefreshStat

                With refreshstruct
                    .ID_STAT = stat
                    .STR_HOSTNAME = Globals.ServerName
                    .STR_VALUE = value
                End With

                If Hosts.ContainsKey(globals.ServerName) Then
                    Hosts.Item(Globals.ServerName).Refresh(refreshstruct)
                Else
                    Dim Host As ServerData = New ServerData(Globals.ServerName, Globals.ServerName, Globals.ServerPort, Globals.isLoginServer)
                    Hosts.Add(Globals.ServerName, Host)
                End If

            End Sub

            Public Shared Sub RemoteStatUpdate(info As RefreshStat)

                If Not Hosts.ContainsKey(info.STR_HOSTNAME) Then Exit Sub

                Dim host As ServerData = Hosts.Item(info.STR_HOSTNAME)
                If Not host Is Nothing Then
                    host.Refresh(info)
                End If

            End Sub

            Public Shared Function ReadInt64(hostname As String, stat As ServerStat) As Int64

                If Not Hosts.ContainsKey(hostname) Then Return 0

                Dim host As ServerData = Hosts.Item(hostname)
                If Not host Is Nothing Then
                    Return Int(host.GetStat(stat).Value)
                Else
                    Return 0
                End If

            End Function

            Public Shared Function ReadGlobalInt64(stat As ServerStat) As Int64

                Dim r As Int64 = 0
                Dim E As SortedDictionary(Of String, ServerData).Enumerator = Hosts.GetEnumerator()

                Do While E.MoveNext
                    r += E.Current.Value.GetStat(stat).Value
                Loop

                Return r

            End Function

            Public Shared Function ReadString(hostname As String, stat As ServerStat) As String

                If Not Hosts.ContainsKey(hostname) Then Return ""

                Dim host As ServerData = Hosts.Item(hostname)
                If Not host Is Nothing Then
                    Return host.GetStat(stat).Value
                Else
                    Return ""
                End If

            End Function

        End Class

        Public Sub UpdateLocalInteger(stat As ServerStat, value As Int64)

            Dim pkt As New Packet
            pkt.Username = Globals.ServerName
            pkt.PacketType = InterServerPacketType.RefreshStat
            pkt.PacketOpCode = PacketOpCode.Notify
            
            Dim refreshstat As New RefreshStat
            With refreshstat
                .ID_STAT = stat
                .STR_HOSTNAME = Globals.ServerName
                .INT_VALUE = value
            End With

            Socket.ClusterComms.UDPBrocast.SendData(Protocol.MakePacket(pkt, refreshstat))
            ClusterStats.LocalStatUpdate(stat, value)

        End Sub

        Public Sub UpdateLocalString(stat As ServerStat, value As String)

            Dim pkt As New Packet
            pkt.Username = Globals.ServerName
            pkt.PacketType = InterServerPacketType.RefreshStat
            pkt.PacketOpCode = PacketOpCode.Notify
            
            Dim refreshstat As New RefreshStat
            With refreshstat
                .ID_STAT = stat
                .STR_HOSTNAME = Globals.ServerName
                .STR_VALUE = value
            End With

            Socket.ClusterComms.UDPBrocast.SendData(Protocol.MakePacket(pkt, refreshstat))
            ClusterStats.LocalStatUpdate(stat, value)

        End Sub

        Public Sub Refresh()

            UpdateLocalString(ServerStat.Uptime, (DateTime.Now - Globals.UpSince).TotalHours.ToString("#.##"))
            UpdateLocalString(ServerStat.MaintainanceMode, Globals.bUnderMaintainance.ToString())
            UpdateLocalInteger(ServerStat.ServerFPS, Globals.iFPS)
            UpdateLocalInteger(ServerStat.UserCount, Socket.SockServer.ConnectionList.Count)

        End Sub

        Public Function GenerateXML() As String

            On Error GoTo err

            Dim XMLDoc As New Xml.XmlDocument

            Dim rootNode As Xml.XmlNode = XMLDoc.CreateElement("MirEvoServerStats")
            XMLDoc.AppendChild(rootNode)

            Dim clusterNode As Xml.XmlNode = XMLDoc.CreateElement("Cluster")

            Dim statNode As Xml.XmlNode = XMLDoc.CreateElement((ServerStat.UserCount.ToString()))
            statNode.InnerText = ClusterStats.ReadGlobalInt64(ServerStat.UserCount)
            clusterNode.AppendChild(statNode)

            statNode = XMLDoc.CreateElement("LoginServerName")
            statNode.InnerText = globals.ServerName
            clusterNode.AppendChild(statNode)

            rootNode.AppendChild(clusterNode)

            For Each host As ServerData In ClusterStats.Hosts.Values

                Dim serverNode As Xml.XmlNode = XMLDoc.CreateElement(host.ServerName)

                statnode = XMLDoc.CreateElement("Hostname")
                statnode.InnerText = host.HostName.ToString()
                serverNode.AppendChild(statNode)

                statNode = XMLDoc.CreateElement("ListenPort")
                statnode.InnerText = host.Port.ToString()
                serverNode.AppendChild(statNode)

                For Each stat As ServerStatistics In host.Stats.Values

                    statNode = XMLDoc.CreateElement(stat.StatType.ToString())
                    statNode.InnerText = stat.Value.ToString()
                    serverNode.AppendChild(statNode)

                Next

                rootNode.AppendChild(serverNode)

            Next

            Dim stringWriter As StringWriter = New StringWriter()
            Dim xmlTextWriter As XmlTextWriter = New XmlTextWriter(stringWriter)

            xmlTextWriter.Formatting = Formatting.Indented
            XMLDoc.WriteTo(xmlTextWriter)

            Return stringWriter.ToString()

err:
            Return "Error!"

        End Function

    End Module

End Namespace

Namespace Socket.HTTPStats.Reporter

    Module mHTTPStatsSocket

        Private Sub HTTPRequest(ByRef query As String, ByRef Conn As EvoLib.SocketServer.Connection)

            'format URI into ASCII/UTF-8 URL string
            Dim Arg_Q As String = LCase(GetArg(query, "q"))
            Dim Arg_HostnameStr As String = GetArg(query, "host")


            ' Dim RequestListener As HttpListenerRequest '= HttpListenerRequest(query)

            Dim R As String = ""

            Select Case Arg_Q

                Case "usercount"
                    Dim hostnames As String = Arg_HostnameStr
                    Dim host As String, hostusers As Int64
                    Dim usercount As Int64 = 0
                    If hostnames = "" Then
                        usercount = Data.ClusterStats.ReadGlobalInt64(ServerStat.UserCount)
                    Else
                        Do Until hostnames.Length = 0
                            host = ReadNextValue(hostnames)
                            hostusers = Data.ClusterStats.ReadInt64(host, ServerStat.UserCount)
                            usercount += hostusers
                        Loop
                    End If
                    R = usercount.ToString()

                Case "userlist"
                    R = Data.OnlineUsers.OnlineUsersString

                Case "xml"
                    R = Data.GenerateXML()

                Case "clientversion"
                    R = Protocol.sClientVersion


            End Select


            If R.Length > 0 Then
                SockServer.SendData("HTTP/1.1 200" & vbCrLf & "Content-Length: " & R.Length & vbCrLf & vbCrLf & R, Conn.Index)
            Else
                SockServer.SendData("HTTP/1.1 400" & vbCrLf & vbCrLf, Conn.Index)
            End If



        End Sub

        Private Function GetArg(query As String, name As String) As String

            Dim str As String = query
            'str = Right(str, str.Length - InStr(str, "?"))

            Dim p As Integer
            If str.Contains("?" & name & "=") Then p = InStr(str, "?" & name & "=")
            If str.Contains("&" & name & "=") Then p = InStr(str, "&" & name & "=")

            If p > 0 Then
                p += (name.Length + 2)
                Dim endP As Integer = InStr(p, str, "&")
                If endP = 0 Then
                    endP = str.Length
                Else
                    endP -= 1
                End If
                Dim len As Integer = (endP - p) + 1

                Return Mid(str, p, len)
            Else
                Return ""
            End If

        End Function










        Public WithEvents SockServer As SocketServer.SocketServer

        Public Function StartServer(ByVal IP As IPAddress, ByVal iPort As Integer) As Boolean

            Try

                SockServer = New SocketServer.SocketServer
                Return SockServer.Listen(IP, iPort)

            Catch
                Return False
            End Try

        End Function

        Private Sub IncommingData(Data() As Byte, ByRef Conn As EvoLib.SocketServer.Connection) Handles SockServer.onDataArrival

            Dim bRecievedBytes(Data.Length - 1) As Byte
            Array.Copy(Data, bRecievedBytes, Data.Length)
            
            Try

                Dim strRequest As String = System.Text.Encoding.UTF8.GetString(bRecievedBytes)

                If Not Conn.GetValue("SYS_OLDDATA") Is Nothing Then
                    If Conn.GetValue("SYS_OLDDATA").Length > 0 Then
                        strRequest = Conn.GetValue("SYS_OLDDATA") & strRequest
                    End If
                End If

                'If strRequest.Length > 100 Then

                'MsgBox(strRequest)

                'Debug.WriteLine("")
                'Debug.WriteLine("")
                'Debug.WriteLine(Conn.Index)
                'Debug.WriteLine(strRequest)
                'Debug.WriteLine("")
                'Debug.WriteLine("")

                If InStr(strRequest, Chr(13) & Chr(10) & Chr(13) & Chr(10)) > 0 Then

                    Dim tmpstr As String = Left(strRequest, InStr(strRequest, Chr(13) & Chr(10) & Chr(13) & Chr(10)))

                    If strRequest.Length > (tmpstr.Length + 4) Then
                        strRequest = Right(strRequest, strRequest.Length - (tmpstr.Length + 4))
                        strRequest = Trim(strRequest)
                        If strRequest.Length > 0 Then
                            Conn.SetValue("SYS_OLDDATA", strRequest)
                        Else
                            Conn.SetValue("SYS_OLDDATA", "")
                        End If
                    End If

                    Debug.WriteLine("HTTP IN:" & vbCrLf & Chr(34) & tmpstr & Chr(34))

                    Dim cmd As String = ReadNextValue(tmpstr)

                    If cmd = "GET" Then

                        Dim query As String = ReadNextValue(tmpstr)
                        HTTPRequest(query, Conn)

                    End If

                Else

                    Conn.SetValue("SYS_OLDDATA", strRequest)

                End If

            Catch

                'incomplete request
                SockServer.ConnectionList.SetIncompleteData(Conn.Index, bRecievedBytes)

            End Try

        End Sub

        Public Sub Shutdown()

            If Not SockServer Is Nothing Then
                SockServer = Nothing
            End If

        End Sub

    End Module

End Namespace